#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>

#define GRADE 21
std::vector<cv::Point2f> control_points;

void mouse_handler(int event, int x, int y, int flags, void *userdata)
{
    if (event == cv::EVENT_LBUTTONDOWN && control_points.size() < GRADE)
    {
        std::cout << "Left button of the mouse is clicked - position (" << x << ", "
                  << y << ")" << '\n';
        control_points.emplace_back(x, y);
    }
}

void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window)
{
    auto &p_0 = points[0];
    auto &p_1 = points[1];
    auto &p_2 = points[2];
    auto &p_3 = points[3];

    for (double t = 0.0; t <= 1.0; t += 0.001)
    {
        auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
                     3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;

        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t, cv::Mat &window)
{
    std::vector<cv::Point2f> points;
    for (int i = 0; i < control_points.size() - 1; i++)
    {
        auto &pS = control_points[i];
        auto &pE = control_points[i + 1];
        auto pR = (1.0 - t) * pE + t * pS;
        points.push_back(pR);
        //window.at<cv::Vec3b>(pR.y, pR.x)[2] = 255;
        cv::circle(window, pR, 2, {0, 255, 255}, 2);
    }
    if (points.size() > 1)
    {
        for (int i = 0; i < points.size() - 1; i++)
        {
            cv::line(window, points[i], points[i + 1], {255, 0, 255});
        }
        return recursive_bezier(points, t, window);
    }
    return points[0];
}
void mmss(float x, float y, cv::Mat &window);
float dis(float x, float y, float xx, float yy);
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window)
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's
    // recursive Bezier algorithm.
    int key = -1;
    for (double t = 0.0; t <= 1.0; t += 0.001)
    {
        if (key == 27)
        {
            break;
        }
        auto win1 = window.clone();
        cv::Point2f pos = recursive_bezier(control_points, t, win1);
        mmss(pos.x, pos.y, window);
        cv::imshow("Bezier Curve", win1);
        key = cv::waitKey(5);
        // int x = round(pos.x);
        // int y = round(pos.y);
        // window.at<cv::Vec3b>(y, x)[1] = 255;
    }
}
void mmss(float x, float y, cv::Mat &window)
{
    int xS = floor(x - 1);
    int xE = floor(x + 1);
    int yS = floor(y - 1);
    int yE = floor(y + 1);
    for (int u = xS; u <= xE; u++)
    {
        for (int v = yS; v <= yE; v++)
        {
            float distance = dis(u + 0.5, v + 0.5, x, y);
            double c = window.at<cv::Vec3b>(v, u)[1] + 255 * std::max(0.0, std::min(1.0, (1.0 - distance / sqrt(2))));
            window.at<cv::Vec3b>(v, u)[1] = std::min(255.0, c);
        }
    }
}
float dis(float x, float y, float xx, float yy)
{
    return sqrt(pow(x - xx, 2) + pow(y - yy, 2));
}
int main()
{
    cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));
    cv::cvtColor(window, window, cv::COLOR_BGR2RGB);
    cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);
    int key = -1;
    //////////////////
    // int a=300;
    // int b=300;
    // int t=0;
    // int k=1;

    // while (key != 27) {
    // 	cv::circle(window,cv::Point2f(b,a),k,CV_RGB(255,0,255),3);//画圆
    // 	cv::imshow("Bezier Curve", window);
    // 	key = cv::waitKey(20); //20ms一帧图像
    // 	a+=2;
    // 	b+=1;
    // 	k++;//改变控制圆大小
    // }
    /////////////////

    cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);

    while (key != 27)
    {
        for (auto &point : control_points)
        {
            cv::circle(window, point, 3, {255, 255, 255}, 3);
        }
        if (control_points.size() > 1)
        {
            for (int i = 0; i < control_points.size() - 1; i++)
            {
                cv::line(window, control_points[i], control_points[i + 1], {255, 0, 255});
            }
        }

        if (control_points.size() == GRADE)
        {
            //naive_bezier(control_points, window);
            bezier(control_points, window);

            cv::imshow("Bezier Curve", window);
            //cv::imwrite("my_bezier_curve.png", window);
            key = cv::waitKey(0);

            return 0;
        }

        cv::imshow("Bezier Curve", window);
        key = cv::waitKey(20);
    }

    return 0;
}
